﻿// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="ShowInNavigation.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the ShowInNavigation type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace Collaboris.Saf.Actions.Moss.Navigation
{
    using System;
    using System.Collections.Generic;

    using Entities;
    using Engine;

    using SharePoint.Wss.Attributes;

    using Utils;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Publishing;

    using SharePoint.Moss.Publishing;
    using SharePoint.Wss.Webs;

    [CollaborisClass(
          Title = "Show In Navigation",
          ReleaseStatus = ReleaseStatus.Beta,
          UndoImplemented = true,
          DisableDocumentation = false,
          ContactAuthorEmail = "Support@Collaboris.co.uk",
          Summary = "Allows the current site to be included or excluded from the global and/or current navigation.",
          ClassGuid = "{156E17A5-A7BA-4e1f-8A48-1BC341A7C615}"
      )
    ]
    public class ShowInNavigation : MOSSActionBase<NavigationSetting>
    {
        #region Fields
        
        /// <summary>
        /// Setup the Action Name
        /// </summary>
        //private const string ActionName = "Collaboris.SharePoint.Actions.MOSS.Navigation.SetNavigation";

        /// <summary>
        /// 
        /// </summary>
        private PreviousNavigationSettings previousNavigationSettings;

        #endregion Fields 

        #region Constructors 

        /// <summary>
        /// Initializes a new instance of the <see cref="ShowInNavigation"/> class.
        /// </summary>
        public ShowInNavigation()
        {
            Scope = SPScope.Web;
        }

        #endregion Constructors 

        #region Properties

        /// <summary>
        /// Gets or sets PreviousNavSettings.
        /// </summary>
        /// <value>
        /// The previous nav settings.
        /// </value>
        public PreviousNavigationSettings PreviousNavSettings
        {
            get
            {
                if (this.previousNavigationSettings == null)
                {
                    this.previousNavigationSettings = new PreviousNavigationSettings(); 
                }

                return this.previousNavigationSettings;
            }

            set
            {
                this.previousNavigationSettings = value;
            }
        }

        #endregion Properties 

        #region Methods

        /// <summary>
        /// This will automatically ExcludeFromNav or IncludeInNav a Publishing Site in the Global and / or current Navigation.
        /// It will also (optionally) apply the settings the sub sites.
        /// </summary>
        /// <param name="context">A valid <see cref="SafContext"/> containing properties to run the Action with.</param>
        /// <param name="entity">The entity.</param>
        /// <returns>
        /// 	<see cref="ProcessStatus"/> describing the outcome of the Action.
        /// </returns>
        protected override ProcessStatus DoAction(SafContext context, NavigationSetting entity)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "ShowInNavigation", "DoAction");

            ValidationHelper.VerifyObjectArgument(context.Web, "context.Web");

            ProcessStatus status = this.RunSetNavigation(entity, context.Web, ProcessMode.Do);

            TraceHelper.TraceMethodEnd(TraceSwitch, "ShowInNavigation", "DoAction");

            return status;
        }

        /// <summary>
        /// Sets the Navigation settings back to what they were before the "Do"
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="entity">The data entityList.</param>
        /// <returns>
        /// A <see cref="ProcessStatus"/> indicating the outcome of the Undo on the Action.
        /// </returns>
        /// <remarks>
        /// 	<para>
        /// This Action wll remember the Navigation settings before it runs the "Do"
        /// and attempt set it back on "UnDo"
        /// </para>
        /// 	<para>
        /// Note if 2 "Dos" are run after the other (without an "undo") then the previoust
        /// state of the navigation is overwritten.
        /// </para>
        /// </remarks>
        protected override ProcessStatus UndoAction(SafContext context, NavigationSetting entity)
        {
            this.Trace.TraceMethodStart("UndoAction");

            ValidationHelper.VerifyObjectArgument(context.Web, "context.Web");

            ProcessStatus status = this.RunSetNavigation(entity, context.Web, ProcessMode.Undo);

            this.Trace.TraceMethodEnd("UndoAction");

            return status;
        }

        /// <summary>
        /// Checks the Data entity used by the Action for Validity
        /// </summary>
        /// <param name="context">The <see cref="SafContext"/> passed in by the SAF Engine.</param>
        /// <param name="entity">The data entity to Validate.</param>
        /// <param name="errors">List of <see cref="ValidationError"/>s</param>
        /// <param name="mode"><see cref="ProcessMode"/>signalling wheter we are Doing, or Undoing the action.</param>
        /// <returns>false if invalid; otherwise true.</returns>
        protected override bool IsValid(SafContext context, NavigationSetting entity, ref List<ValidationError> errors, ProcessMode mode)
        {
            Trace.TraceMethodStart("IsValid()");
            int currentCount = errors.Count;

            // Validate each object now.
            if (!entity.PagesAction.HasValue)
                entity.PagesAction = NavigationAction.DoNothing;

            if (!entity.SitesAction.HasValue)
                entity.SitesAction = NavigationAction.DoNothing;
            
            if (!entity.ApplyToCurrentNav.HasValue)
                entity.ApplyToCurrentNav = false;

            if (!entity.ApplyToGlobalNav.HasValue)
                entity.ApplyToGlobalNav = false;

            // Have we added errors ?
            Trace.TraceMethodEnd("IsValid()");

            return errors.Count == currentCount;                        
        }

        /// <summary>
        /// Applies the nav setting the Current Navigation.
        /// </summary>
        /// <param name="parentWeb">The parent web.</param>
        /// <param name="itemID">The item ID.</param>
        /// <param name="itemDesc">The item desc.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="navType">Type of the nav.</param>
        /// <param name="itemUrl">The item URL.</param>
        private void ApplyCurrentNavChange(
                PublishingWeb parentWeb,
                Guid itemID,
                string itemDesc,
                ProcessMode mode,
                NavigationSetting entity,
                NavType navType,
                string itemUrl)
        {
            Trace.TraceMethodStart("ApplyCurrentNavChange()");

            NavigationAction? action = this.EvalExclude(itemUrl, mode, entity, navType, NavSettingScope.Current, parentWeb);

            // Depending on whether we are excluded or not.
            if (action == NavigationAction.ExcludeFromNav)
            {
                Trace.TraceVerbose("Excluding this item '{0}' from the Current Navigation", itemDesc);

                parentWeb.ExcludeFromNavigation(false, itemID);
            }

            if (action == NavigationAction.IncludeInNav)
            {
                Trace.TraceVerbose("Including this item '{0}' into the Current Navigation", itemDesc);

                parentWeb.IncludeInNavigation(false, itemID);
            }

            if (action == NavigationAction.DoNothing)
            {
                Trace.TraceVerbose("No Action will be taken for this item '{0}' the Current Navigation", itemDesc);
            }

            Trace.TraceMethodEnd("ApplyCurrentNavChange()");
        }

        /// <summary>
        /// Applies the nav setting the Global Navigation.
        /// </summary>
        /// <param name="parentWeb">
        /// The parent web.
        /// </param>
        /// <param name="itemID">
        /// The item ID.
        /// </param>
        /// <param name="itemDesc">
        /// The item desc.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <param name="entity">
        /// The entity.
        /// </param>
        /// <param name="navType">
        /// The nav Type.
        /// </param>
        /// <param name="itemUrl">
        /// The item Url.
        /// </param>
        private void ApplyGlobalNavChange(
                PublishingWeb parentWeb,
                Guid itemID,
                string itemDesc,
                ProcessMode mode,
                NavigationSetting entity,
                NavType navType,
                string itemUrl)
        {
            Trace.TraceMethodStart("ApplyGlobalNavChange()");

            NavigationAction? action = this.EvalExclude(itemUrl, mode, entity, navType, NavSettingScope.Global, parentWeb);

            if (action.Value == NavigationAction.ExcludeFromNav)
            {
                Trace.TraceVerbose("Excluding this item '{0}' from the Global Navigation", itemDesc);

                parentWeb.ExcludeFromNavigation(true, itemID);
            }

            if (action.Value == NavigationAction.IncludeInNav)
            {
                Trace.TraceVerbose("Including this item '{0}' into the Global Navigation", itemDesc);

                parentWeb.IncludeInNavigation(true, itemID);
            }

            if (action.Value == NavigationAction.DoNothing)
            {
                Trace.TraceVerbose("No Action will be taken for this item '{0}' the Global Navigation", itemDesc);
            }

            Trace.TraceMethodEnd("ApplyGlobalNavChange()");
        }

        /// <summary>
        /// Excludes or Includes a Pub page or Pub Site from the parentWebs
        /// Global or Current Navigation.
        /// <see cref="NavigationSetting"/> passed into the Action.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="parentWeb">The parent web.</param>
        /// <param name="itemID">The item ID.</param>
        /// <param name="itemDesc">The item desc.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="navType">Type of the nav.</param>
        /// <param name="itemUrl">The item URL.</param>
        private void ApplyNavChange(
                NavigationSetting entity,
                PublishingWeb parentWeb,
                Guid itemID,
                string itemDesc,
                ProcessMode mode,
                NavType navType,
                string itemUrl)
        {
            Trace.TraceMethodStart("ApplyNavChange()");

            ValidationHelper.VerifyObjectArgument(parentWeb, "parentWeb");
            ValidationHelper.VerifyObjectArgument(itemID, "itemID");           

            // apply the changes to the current nav
            if (entity.ApplyToCurrentNav.HasValue)
            {
                if (entity.ApplyToCurrentNav.Value)
                {
                    this.ApplyCurrentNavChange(parentWeb, itemID, itemDesc, mode, entity, navType, itemUrl);
                }
            }
            // apply the changes to the Global Nav
            if (entity.ApplyToGlobalNav.HasValue)
            {
                if (entity.ApplyToGlobalNav.Value)
                {
                    this.ApplyGlobalNavChange(parentWeb, itemID, itemDesc, mode, entity, navType, itemUrl);
                }
            }

            Trace.TraceMethodEnd("ApplyNavChange()");
        }

        /// <summary>
        /// Determines whether to exclude the Item from the Nav Settings.
        /// </summary>
        /// <param name="itemUrl">The item URL.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="navType">Type of the nav.</param>
        /// <param name="scope">The scope.</param>
        /// <param name="parentWeb">The parent web.</param>
        /// <returns></returns>
        /// <remarks>
        /// If Do :
        /// - will also Save the previous setting to the Property Bag.
        /// If Undo :
        /// - will return the previous setting from the property Bag
        /// </remarks>
        private NavigationAction? EvalExclude(
                string itemUrl,
                ProcessMode mode,
                NavigationSetting entity,
                NavType navType,
                NavSettingScope scope,
                PublishingWeb parentWeb)
        {
            NavigationAction? exclude = NavigationAction.DoNothing;

            if (mode == ProcessMode.Do)
            {
                this.SaveCurrentValue(itemUrl, scope, parentWeb);
                exclude = this.IsExcluded(entity, navType);
            }
            else
            {
                exclude = this.WasItExcluded(itemUrl, scope);
            }

            return exclude;
        }

        /// <summary>
        /// Determines whether the specified entity is excluded.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="navType">Type of the nav.</param>
        /// <returns>
        /// 	<c>true</c> if the specified entity is excluded; otherwise, <c>false</c>.
        /// </returns>
        private NavigationAction? IsExcluded(NavigationSetting entity, NavType navType)
        {
            Trace.TraceMethodStart("IsExcluded()");
            NavigationAction? exclude = NavigationAction.DoNothing;
            if (navType == NavType.Page)
            {
                exclude = entity.PagesAction;
            }

            if (navType == NavType.Site)
            {
                exclude = entity.SitesAction;                
            }

            Trace.TraceMethodEnd("IsExcluded()");
            return exclude;
        }

        /*
        /// <summary>
        /// Process the Action for either Do or Undo
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="entity">The entity.</param>
        /// <returns></returns>
        private ProcessStatus ProcessAction(SafContext context, ProcessMode mode, NavigationSetting entity)
        {
            Trace.TraceMethodStart("ProcessAction()");

            // Do some Initial Tracing
            DoTracing(this.Name, mode, entity);                      

            // Attempt to get the Action context
            // //SafContext ctx = GetContext(context);

            // get the current Site
            SPWeb currentWeb = context.Web;
            ValidationHelper.VerifyObjectArgument(currentWeb, "currentWeb");

            // Go and Set the Publish Page
            ProcessStatus status = this.RunSetNavigation(entity, currentWeb, ProcessMode.Do);

            Trace.TraceMethodEnd("ProcessAction()");

            return status;
        }
        */

        /// <summary>
        /// Sets the Navigation up for the web.
        /// </summary>
        /// <param name="entity">NavigationSetting entity.</param>
        /// <param name="web">The web.</param>
        /// <param name="mode">ProcessMode - Do or Undo</param>
        /// <returns></returns>
        private ProcessStatus RunSetNavigation(NavigationSetting entity, SPWeb web, ProcessMode mode)
        {
            Trace.TraceMethodStart("RunSetNavigation()");

            // use the current Site.
            
            TargetWeb.AllowUnsafeUpdates = true;

            // This is an undo, so lets load in the old State.
            if (mode == ProcessMode.Undo)
                this.PreviousNavSettings = this.GetProperty<PreviousNavigationSettings>(TargetWeb, "PreviousNavigationSettings");
            else
                this.DeleteProperty(TargetWeb, "PreviousNavigationSettings");

            Trace.TraceVerbose("Getting the Publishing Site");
            PublishingWeb pubweb = PublishingWeb.GetPublishingWeb(TargetWeb);
            ValidationHelper.VerifyObjectArgument(pubweb, "pubWeb");

            // Shows / Hides the pages
            this.ShowPages(entity, mode, pubweb);
            
            // Shows / Hides the Sub Sites
            this.ShowSites(entity, mode, pubweb);

            pubweb.Update();

            // Its a do, so lets save the Current Settigns to the Prop Bag
            if (mode == ProcessMode.Do)
                this.SaveProperty<PreviousNavigationSettings>(TargetWeb, "PreviousNavigationSettings", this.PreviousNavSettings);

            if (mode == ProcessMode.Undo)
                this.DeleteProperty(TargetWeb, "PreviousNavigationSettings");

            // Switch Back to Safe Mode.                
            TargetWeb.Update();
            TargetWeb.AllowUnsafeUpdates = false;
            

            Trace.TraceMethodEnd("RunSetNavigation()");

            return ProcessStatus.Success;
        }

        /// <summary>
        /// Saves the current value.
        /// </summary>
        /// <param name="nodeUrl">
        /// The node Url.
        /// </param>
        /// <param name="navSettingScope">
        /// The nav setting scope.
        /// </param>
        /// <param name="parentWeb">
        /// The parent Web.
        /// </param>
        private void SaveCurrentValue(string nodeUrl, NavSettingScope navSettingScope, PublishingWeb parentWeb)
        {
            Trace.TraceMethodStart("SaveCurrentValue()");
            bool inGlobal = false;
            bool inCurrent = false;

            PublishingWebHelper.IsItemInNavigation(parentWeb, nodeUrl, ref inCurrent, ref inGlobal);

            // only add it once
            if (this.PreviousNavSettings.Contains(nodeUrl) == false)
            {
                PreviousNavigationVisibility setting = new PreviousNavigationVisibility();
                setting.Url = nodeUrl;
                setting.ShowInGlobal = inGlobal;
                setting.ShowInCurrent = inCurrent;
                this.PreviousNavSettings.Add(setting);
            }

            Trace.TraceMethodEnd("SaveCurrentValue()");
        }

        /// <summary>
        /// Shows or hides the pages from the Current and / or Global Nav
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="pubweb">The pubweb.</param>
        private void ShowPages(NavigationSetting entity, ProcessMode mode, PublishingWeb pubweb)
        {
            Trace.TraceMethodStart("ShowPages()");
            
            // hides the pages
            if (entity.PagesAction == NavigationAction.IncludeInNav ||
                entity.PagesAction == NavigationAction.ExcludeFromNav)
            {
                Trace.TraceVerbose("Applying Navigational changes to the Pages");
                foreach (PublishingPage subPage in pubweb.GetPublishingPages())
                {
                    this.ApplyNavChange(entity, pubweb, subPage.ListItem.UniqueId, subPage.ListItem.Url, mode, NavType.Page, subPage.Url);
                }
            }

            Trace.TraceMethodEnd("ShowPages()");
        }

        /// <summary>
        /// Shows or hides the sites from the Current and / or Global Nav
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="mode">The mode.</param>
        /// <param name="pubweb">The pubweb.</param>
        private void ShowSites(NavigationSetting entity, ProcessMode mode, PublishingWeb pubweb)
        {
            Trace.TraceMethodStart("ShowSites()");
            if (entity.SitesAction == NavigationAction.IncludeInNav ||
                entity.SitesAction == NavigationAction.ExcludeFromNav)
            {
                Trace.TraceVerbose("Applying Navigational changes to the Current Webs");
                foreach (PublishingWeb subWeb in pubweb.GetPublishingWebs())
                {
                    this.ApplyNavChange(entity, pubweb, subWeb.Web.ID, subWeb.Url, mode, NavType.Site, subWeb.Url);
                }
            }

            Trace.TraceMethodEnd("ShowSites()");
        }

        /// <summary>
        /// Wases it excluded.
        /// </summary>
        /// <param name="nodeUrl">The node URL.</param>
        /// <param name="scope">The scope.</param>
        /// <returns></returns>
        private NavigationAction WasItExcluded(string nodeUrl, NavSettingScope scope)
        {
            Trace.TraceMethodStart("WasItExcluded()");
            PreviousNavigationVisibility setting = this.PreviousNavSettings[nodeUrl];
            if (setting == null)
            {
                return NavigationAction.DoNothing;
            }

            if (scope == NavSettingScope.Current)
            {
                Trace.TraceMethodEnd("WasItExcluded()");
                if (setting.ShowInCurrent)
                    return NavigationAction.IncludeInNav;
                
                return NavigationAction.ExcludeFromNav;
            }

            Trace.TraceMethodEnd("WasItExcluded()");
            if (setting.ShowInGlobal)
                return NavigationAction.IncludeInNav;

            Trace.TraceMethodEnd("WasItExcluded()");

            return NavigationAction.ExcludeFromNav;            
        }

        #endregion Methods 
    }
}